<?php
/**
 * DB Class
 *
 * Requires PHP5.0.4 or later
 */

session_set_cookie_params(259200);
session_start();

define('OBJECT', 'OBJECT', true);
define('ARRAY_A', 'ARRAY_A', false);
define('ARRAY_N', 'ARRAY_N', false);

/**
* Contains database functions and handles connecting to the database and
* making queries. The $db object will be our primary interface to the
* database. Assume you always have one.
*/
class db {

	private $show_errors = false;
	private $num_queries = 0;	
	private $last_query;
	private $col_info;
	private $queries;

	// ==================================================================
	//	DB Constructor - connects to the server and selects a database

/** db($dbuser, $dbpassword, $dbname, $dbhost)
* Usually this will be called for you. This is the constructor method
* for the db class, and establishes the database connection
* However, if you wish to make a connection to some other database, you
* can explicitly grab a database handle thusly:
* <code>
* $mydb = new db(DB_USER, DB_PASSWORD, DB_NAME, DB_HOST);
* </code>
* Note that if you're trying to connect to a mssql database, this
* technique will not work, and you must use sybase_connect().
* 
* @param string $dbuser
* @param string $dbpassword
* @param string $dbname
* @param string $dbhost
* @param boolean $newlink Should we create a completely separate handle?
*/
	function db($dbuser, $dbpassword, $dbname, $dbhost, $newlink=0) {
		$this->dbh = @mysql_connect($dbhost, $dbuser, $dbpassword, $newlink);
		if (!$this->dbh) {
			$this->bail("<h1>Error establishing a database connection</h1>
                  <p>The error message was: <b>" . mysql_error() . "</b></p> ");
		}

		$this->select($dbname);
	} 
/** $db->select($db)
* Selects (or changes) what database you are talking to.
*/
	public function select($db) {
		if (!@mysql_select_db($db, $this->dbh)) {
			$this->bail("<h1>Can&#8217;t select database</h1><b>" 
                         . mysql_error() . "</b></p>");
		}
	}

/** $db->escape($str)
* Escape a string for safe database insertion.
* 
* @param string $str String to be escaped
* @return string Escaped string
*/
	function escape($str) {
		return addslashes($str);				
	}

/** $db->unescape($str)
* Remove escaping on a string after it comes out of the database
* 
* @param string $str
* @return string escaped string
*/
	function unescape($str) {
		return stripslashes($str);				
	}

/** $db->print_error($str)
* Print SQL/DB error.
* 
* @param string $str Error message
* @return boolean
*/

	function print_error($str = '') {
		if (!$str) $str = mysql_error();

		// Is error output turned on or not..
		if ( $this->show_errors ) {
			// If there is an error then take note of it
			print "<div id='error'>
			<p class='dberror'><strong>database error:</strong> [$str]<br />
			<code>$this->last_query</code></p>
			</div>";
			return true;
		} else {
            error_log(mysqlnow() .  ' Database error on query: ' . $this->last_query . ': ' . mysql_error($this->dbh), 0);
			return false;	
		}
	}

/** $db->flush()
* Kill cached query results
*/

	function flush() {
		$this->last_result = null;
		$this->col_info = null;
		$this->last_query = null;
	}

/** $db->safe_query( $query, $arg1, $arg2, ... );
*
* Makes the arguements to an SQL statement safer. The query itself needs
* to be provided in template format, like:
* <code>
* $safe_query = $db->safe_query(
*     "SELECT * FROM foo WHERE bar = '%s'",
*     'wooga' );
* </code>
* Then you can use $safe_query in your queries.
* Use this function when you've received input from the end user that
* you don't trust.
* 
* @param string $query, string argument, string argument, ...
* @return string $safe_query
*/
function safe_query() {
   $args = func_get_args();
   $num  = func_num_args() - 1;
   $query = array_shift( $args );
   for ($i=0; $i < $num; $i++) {
       $args[$i] = mysql_real_escape_string($args[$i]);
   }

   // Have to do this because you can't pass an array to sprintf. Oy.
   array_unshift( $args, $query );
   $safe_query = call_user_func_array( 'sprintf', $args);

   return $safe_query;
}

/** $db->query("SQL Query Goes Here");
* Performs an SQL query
* 
* @param string $query
* @return mixed false if the query fails, number of rows returned if it is successful
*/
	function query($query) {
		// initialise return
		$return_val = 0;
		$this->flush();

		// Log how the function was called
		$this->func_call = "\$db->query(\"$query\")";

		// Keep track of the last query for debug..
		$this->last_query = $query;

		// Perform the query via std mysql_query function..
		$this->result = @mysql_query($query, $this->dbh);
		++$this->num_queries;

		// If there is an error then take note of it..
		if ( mysql_error() ) {
			$this->print_error();
			return false;
		}

		if ( preg_match("/^\\s*(insert|delete|update|replace) /i",$query) ) {
			$this->rows_affected = mysql_affected_rows();
			// Take note of the insert_id
			if ( preg_match("/^\\s*(insert|replace) /i",$query) ) {
				$this->insert_id = mysql_insert_id($this->dbh);	
			}
			// Return number of rows affected
			$return_val = $this->rows_affected;
		} else {
			$i = 0;
			while ($i < @mysql_num_fields($this->result)) {
				$this->col_info[$i] = @mysql_fetch_field($this->result);
				$i++;
			}
			$num_rows = 0;
			while ( $row = @mysql_fetch_object($this->result) ) {
				$this->last_result[$num_rows] = $row;
				$num_rows++;
			}

			@mysql_free_result($this->result);

			// Log number of rows the query returned
			$this->num_rows = $num_rows;
			
			// Return number of rows selected
			$return_val = $this->num_rows;
		}

		return $return_val;
	}

/** $obj = $db->get_row("Query", [OBJECT|ARRAY_A|ARRAY_N], [offset])
* Returns one row of the database query. By default, this is returned as
* an object which can be queried for field values. You can further
* specify an offset if you want other than the first record in the query
* set.
* 
* @param string $query
* @param const $output Desired return format
* @param int $y Offset
*/
	function get_row($query = null, $output = OBJECT, $y = 0) {
		$this->func_call = "\$db->get_row(\"$query\",$output,$y)";
		if ( $query )
			$this->query($query);

		if ( $output == OBJECT ) {
			return $this->last_result[$y] ? $this->last_result[$y] : null;
		} elseif ( $output == ARRAY_A ) {
			return $this->last_result[$y] ? get_object_vars($this->last_result[$y]) : null;
		} elseif ( $output == ARRAY_N ) {
			return $this->last_result[$y] ? array_values(get_object_vars($this->last_result[$y])) : null;
		} else {
			$this->print_error(" \$db->get_row(string query, output type, int offset) -- Output type must be one of: OBJECT, ARRAY_A, ARRAY_N");
		}
	}


/** $obj = $db->get_results($query, [OBJECT]);
* Return the the query as a result set - see docs for more details
* 
* @param sring $query
* @param const $output Desired return format
* @return mixed
*/
	function get_results($query = null, $output = OBJECT) {
		$this->func_call = "\$db->get_results(\"$query\", $output)";

		if ( $query )
			$this->query($query);

		// Send back array of objects. Each row is an object
		if ( $output == OBJECT ) {
			return $this->last_result;
		} elseif ( $output == ARRAY_A || $output == ARRAY_N ) {
			if ( $this->last_result ) {
				$i = 0;
				foreach( $this->last_result as $row ) {
					$new_array[$i] = (array) $row;
					if ( $output == ARRAY_N ) {
						$new_array[$i] = array_values($new_array[$i]);
					}
					$i++;
				}
				return $new_array;
			} else {
				return null;
			}
		}
	}

/** $db->bail($message);
* Just wraps errors in a nice header and footer
*/
	function bail($message) { 
        if ( !$this->show_errors ) {
            error_log($message);
        }
        header( 'Content-Type: text/html; charset=utf-8');		
        echo $message;
        die();
    }

/** delete('table',$ID);
*  Delete an object from the database
*  Usually called from the object, not directly from here
*/
function delete($table,$id) {
    global $db;
    $db->query("DELETE FROM " . $table . "
            WHERE ID = " . $id );
}


/** $db->save($object);
* This is abstracted out of every object class so that we don't have
* quite so much code duplication. Every object class's save method
* should just call this, like:
* <code>
* $db->save($this);
* </code>
* This assumes that you have a valid field list. Which, off course, you
* should
*/
function save($object) {
    global $db;
    $set = '';
    $values = '';

    if (isset ($object->_modified)) {
        // Write it to the database
        if (isset($object->_id)) {
            // It's an existing record
            foreach ($object->fields as $field) {
                $object->{"_$field"} = $db->escape($object->{"_$field"});
                $set .= $field . " = '" . $object->{"_$field"} . "', ";
            }
            $set = preg_replace("/, $/", "", $set);
            $db->query("UPDATE " . $object->table . "
            SET " . $set . "
            WHERE ID = " . $object->_id );
       } else {
            // It's a new record
            foreach ($object->fields as $field) {
                $object->{"_$field"} = $db->escape($object->{"_$field"});
                $set .= $field . ", ";
                $values .= "'".$object->{"_$field"} . "', ";
            }
            $set = preg_replace("/, $/", "", $set);
            $values = preg_replace("/, $/", "", $values);
            $db->query("INSERT into " . $object->table . 
             " (".  $set . ") VALUES (" . $values . ")");
            $object->_id = $db->insert_id;
            $object->_modified = 0;
        }
    } else {
        return;
    }
}

} // End of db class declaration

/** getset($object, $method, $args);
* You'll probably never call this from actual code, but it gets called
* as part of the generic get/set data access methods of a database table
* object. $object is the data class object, $method is the data field to
* be get/set. $args is the hash of arguments, which, in practice,
* contains the value, if any, to which the field is to be set.
* 
* @param object $obj
* @param string $method Attribute to be queried on the object
* @param array $args
* @return mixed Value of this attribute of the object
*/
function getset($obj, $method, $args) {
    if (isset ($args[0])) {
        $arg = $args[0];
        $obj->{"_$method"} = $arg;
        $obj->_modified = 1;
        return $arg[0];
    } else {
        return stripslashes($obj->{"_$method"});
    }
}

/** valid_handle($db)
* Test to see if it's a valid database handle. At the moment, we just
* see if it's set. Later, we want to actually reconnect if it's not a
* connected handle.
*/
function valid_handle($db) {
    if (! isset ($db)) {
        print "You really need to have a database handle before loading
        this class, if you want anything to work.";
        exit();
    }
}


/** load_obj($object, $id)
* Loads a data object from a database table record, given the ID number
* of that record. It is assumed that every database table will have a
* field named 'ID' which is unique and auto-incremented, and that
* $object is already an object in a data class which specifies a list of
* fields in the fields attribute.
*/
// load the object from the database by ID
function load_obj($object, $id) {
        global $db;
        $row = $db->get_row("SELECT *
                FROM " . $object->table . "
                WHERE ID = " . $id);
        $object->_id          = $id;
        foreach ($object->fields as $field) {
            $object->{"_$field"} = $db->unescape($row->{$field});
        }
}

function __autoload($class_name) {
    if (stristr($class_name, 'magic_')) {
        require_once INC . 'classes/magic/' . $class_name . '.php';
        return;
    }
    
    require_once INC . 'classes/' . $class_name . '.php';
}

$db = new db(DB_USER, DB_PASSWORD, DB_NAME, DB_HOST);
?>
